#include <assert.h>
#include <float.h>
#include <math.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>


#include <allegro5/allegro.h>
#include <msdl.h>


#define EPSILON 0.001

#define SPEC_START 400
#define SPEC_SAMPLES 8
#define SPEC_END 700
#define SPEC_STEP ((SPEC_END - SPEC_START)/(double)SPEC_SAMPLES)

//#define LMAP_SIZE 10

#define sqr(x) pow((x),2)

#ifdef __GNUC__
#define PACKED __attribute__((__packed__))
#else
#define PACKED
#endif


typedef enum {
	OBJ_SPHERE,
	OBJ_TRIANGLE
} object_type_t;

typedef struct {
	float w[SPEC_SAMPLES]; // Wavelengths
	float i[SPEC_SAMPLES]; // Intensities
} PACKED color_t;

typedef struct {
	double w, x, y, z;
} PACKED vector_t;

// Pointer-compatible with vector_t
typedef struct {
	double t, x, y, z;
	vector_t n;
} PACKED point_t;

typedef struct {
	vector_t o;
	vector_t d;
} PACKED ray_t;

typedef struct material_t material_t;
struct material_t {
#define MAT_EMITS 0x00000001
	uint32_t flags;
	color_t emission;
	color_t diffuse;
	color_t specular;
	color_t transmit;
	color_t ior;
	// Stores in *c the spectrum relfected/refracted from *id to *od
	void (*bsdf)(color_t *c, material_t* m, point_t *p, vector_t *id,
		vector_t *od);
	// Sets *v to a random direction around p
	// Returns the probability density near *v
	float (*sample_bsdf)(vector_t *v, material_t *m, point_t *p, vector_t *id);
} PACKED;

typedef struct {
	float min[3], max[3];
} aabb_t;

typedef struct object_t object_t;
struct object_t {
	object_type_t type;
	material_t mat;
	void (*intersect)(object_t *obj, ray_t *ray, point_t *p);
	void (*surface_point)(point_t *p, object_t *obj);
	void (*aabb)(aabb_t *aabb, object_t *o);
} PACKED;

typedef struct {
	object_t obj;
	vector_t c;
	float r;
} PACKED sphere_t;

typedef struct {
	object_t obj;
	vector_t n, v, s1, s2; // Normal; a vertice, and the two sides touching it
} PACKED triangle_t;

typedef struct {
	double x, y, z;
	vector_t orgin; // (0, 0), relative to position
	vector_t xstep; // (x, y) -> (x+1, y)
	vector_t ystep; // (x, y) -> (x, y+1)
} PACKED camera_t;

typedef struct refrac_stack_t refrac_stack_t;
struct refrac_stack_t {
	refrac_stack_t *next, *prev;
	object_t *o;
	color_t *ior;
};

typedef struct {float w, i;} spectrum_sample_t;

// An unsorted, super-version of color_t for raw data
typedef struct {
	int numsamples;
	spectrum_sample_t *samples;
} spectrum_t;


extern float *lambdas;
extern camera_t camera;
extern int width, height;
extern color_t ambientior;
extern object_t **objects;
extern uint32_t numobjects;
extern refrac_stack_t *refracstack;

extern float ran();
extern void ran_init();
extern void rref_3x4(double **m);
extern double dot(vector_t *v1, vector_t *v2);
extern void add_object(object_t *o);
extern void push_refrac(object_t *o);
extern void pop_refrac(object_t *o);
extern vector_t *normalize_vec(vector_t *v);
extern vector_t *vec_sub(vector_t *v1, vector_t *v2, vector_t *result);
extern vector_t *vec_scale(vector_t *v, double s);

extern void pwld_import(FILE *f);

extern void bih_build();
extern int intersect_ray(ray_t *ray, point_t *point, object_t **object);

extern void random_gray(color_t *c, float intensity);
extern void color_copy(color_t *dest, color_t *src);
extern float color_sample(color_t *c, float lambda);
extern void color_add(color_t *a, color_t *b);
extern void color_sub(color_t *a, color_t *b);
extern void color_mul(color_t *a, color_t *b);
extern void color_scale(color_t *c, float s);
extern void color_exp(color_t *c);
extern void color_rcp(color_t *c);
extern void resample_spectrum(color_t *c, spectrum_t *s);
extern void cieXYZ_init();
extern void rgb_to_color(color_t *c, float r, float g, float b);
extern void color_to_XYZ(float *X, float *Y, float *Z, color_t *c);
extern void XYZ_to_rgb(float *r, float *g, float *b, float *X, float *Y,
	float *Z);
extern void color_to_rgb(float *r, float *g, float *b, color_t *c);

extern void sphere_intersect(object_t *o, ray_t *r, point_t *p);
extern void sphere_surface_point(point_t *p, object_t *o);
extern void sphere_aabb(aabb_t *aabb, object_t *o);

extern void triangle_intersect(object_t *o, ray_t *r, point_t *p);
extern void triangle_surface_point(point_t *p, object_t *o);
extern void triangle_aabb(aabb_t *aabb, object_t *o);

extern void lambert_bsdf(color_t *c, material_t *m, point_t *p, vector_t *id,
	vector_t *od);
extern float lambert_sample_bsdf(vector_t *v, material_t *m, point_t *p,
	vector_t *id);

extern void mirror_bsdf(color_t *c, material_t *m, point_t *p, vector_t *id,
	vector_t *od);
extern float mirror_sample_bsdf(vector_t *v, material_t *m, point_t *p,
	vector_t *id);

extern void fresnel_bsdf(color_t *c, material_t *m, point_t *p, vector_t *id,
	vector_t *od);
extern float fresnel_sample_bsdf(vector_t *v, material_t *m, point_t *p,
	vector_t *id);

